/**
	
                 Springs On Rails
                 Copyright [2007] [Sunny Liu]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.	
*/
package org.springonrails.parser;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import org.springonrails.RailsException;
import org.springonrails.model.Attribute;
import org.springonrails.model.Entity;
import org.springonrails.model.Intermediate;
import org.springonrails.model.Relationship;
import org.springonrails.model.RelationshipColumnPair;
import org.springonrails.model.Target;
import org.springonrails.util.StringUtils;

public class EntityMapDomParser 
{
	private String mapLocation;
	private List<Entity> entities;
	private static Log log = LogFactory.getLog(EntityMapDomParser.class);
	private Properties additionalProperties = new Properties();
	
	
	public EntityMapDomParser(String mapLocation) {
		super();
		this.mapLocation = mapLocation;
		parse();	
	}
	
	public String getMapLocation() {
		return mapLocation;
	}
	public void setMapLocation(String mapLocation) {
		this.mapLocation = mapLocation;
	}
	public List<Entity> getEntities() 
	{		
		return entities;
	}
	
	public void setEntities(List<Entity> entities) {
		this.entities = entities;
	}
	
	
	
	public Properties getAdditionalProperties() {
		return additionalProperties;
	}

	public void setAdditionalProperties(Properties additionalProperties) {
		this.additionalProperties = additionalProperties;
	}
	
	
	protected void cleanupRelationship()
	{
		//Build in memory index;
		final HashMap<String, Entity> map = new HashMap<String, Entity>();
		final HashMap<String, Attribute> attMap = new HashMap<String, Attribute>();
		for(Entity entity:entities)
		{
			map.put(entity.getJavaName(), entity);
			for(Attribute att:entity.getAttributes())
			{
				attMap.put(entity.getJavaName()+"&&" + att.getJavaName(), att);
			}			
		}
		//Massage here;
		for(Entity entity:entities)
		{
			if(entity.isHasRelationships())
			{
				for(Relationship relatoinship:entity.getRelationships())					
				{
					String[] leftProps = relatoinship.getProperties();
					String[] rightProps = relatoinship.getTarget().getProperties();					
					relatoinship.setTargetEntity(map.get(relatoinship.getTarget().getName()));
					if(relatoinship.getIntermediate()!=null){
						relatoinship.setInterEntity(map.get(relatoinship.getIntermediate().getName()));
						//Many-to-many;
						String[] mLeftProps = relatoinship.getIntermediate().getLeftProperties();
						String[] mRightProps = relatoinship.getIntermediate().getRightProperties();
						if((leftProps.length!=mLeftProps.length)
								|| mRightProps.length!=rightProps.length){
							throw new RailsException("Realtionship key number are not match [" + entity.getJavaName() + "]");
						}
						for(int i=0; i<leftProps.length; i++){
							RelationshipColumnPair pair = new RelationshipColumnPair();
							pair.setLeftColumn(attMap.get(entity.getJavaName()+"&&"+leftProps[i]).getColumnName());
							pair.setRightColumn(attMap.get(relatoinship.getIntermediate().getName()+"&&"+mLeftProps[i]).getColumnName());
							relatoinship.addLeftColumnPair(pair);
						}
						for(int i=0; i<rightProps.length; i++){
							RelationshipColumnPair pair = new RelationshipColumnPair();
							pair.setLeftColumn(attMap.get(relatoinship.getIntermediate().getName()+"&&"+mRightProps[i]).getColumnName());
							pair.setRightColumn(attMap.get(relatoinship.getTarget().getName()+"&&"+rightProps[i]).getColumnName());
							relatoinship.addRightColumnPair(pair);
						}					
					}else{
						//one-to-many
						//one-to-one;						
						if(leftProps.length!=rightProps.length){
							throw new RailsException("Realtionship key number are not match [" + entity.getJavaName() + "]");
						}						
						for(int i=0; i<leftProps.length; i++){
							RelationshipColumnPair pair = new RelationshipColumnPair();
							pair.setLeftColumn(attMap.get(entity.getJavaName()+"&&"+leftProps[i]).getColumnName());
							pair.setRightColumn(attMap.get(relatoinship.getTarget().getName()+"&&"+rightProps[i]).getColumnName());
							relatoinship.addLeftColumnPair(pair);
						}
					}
				}
				
			}
		}
		
	}
	
	

	protected void parse()
	{
		if (entities!=null) return;
		this.entities = new ArrayList<Entity>();
		
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document document = null;
			File f = new File(mapLocation);
			if(mapLocation!=null && f.exists()){				
				document = builder.parse( f );
			}else{
				log.warn("Entity Map file not found. " + f.getAbsolutePath());
				document = builder.parse( Thread.currentThread().getContextClassLoader().getResourceAsStream("/META-INF/typeMap.xml") );
			}
			
			NodeList metaElements = document.getDocumentElement().getElementsByTagName("meta");
			int mlen = metaElements.getLength();
			for (int k=0; k<mlen; k++) {
				Element el = (Element)metaElements.item(k);
				String prop = el.getTextContent();
				if(!StringUtils.isEmpty(prop)) 
					try{
						additionalProperties.load(new ByteArrayInputStream(prop.getBytes()));
					}catch(Exception ex){
						;
					}
			}
			
			NodeList entityElements = document.getDocumentElement().getElementsByTagName("entity");
			int len = entityElements.getLength();
			for(int i=0; i<len; i++){
				Element el = (Element)entityElements.item(i);
				Entity entity = new Entity();
				entity.setJavaName(StringUtils.matchEmptyReplaceWithNull(el.getAttribute("javaName")));
				entity.setTableName(StringUtils.matchEmptyReplaceWithNull(el.getAttribute("tableName")));
				entity.setViewLabel(StringUtils.matchEmptyReplaceWithNull(el.getAttribute("viewLabel")));				
							
				NodeList ms = el.getElementsByTagName("methods");
				if(ms!=null && ms.getLength()>0)
				{
					Element method = (Element)ms.item(0);
					entity.setMethods(StringUtils.matchEmptyReplaceWithNull(method.getTextContent()));
				}
				NodeList atts = el.getElementsByTagName("property");
				int count = atts.getLength();
				for(int j=0; j<count; j++){
					Element el1 = (Element)atts.item(j);
					Attribute att = new Attribute();
					att.setFormatString(StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("formatString")));
					att.setJavaName(StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("javaName")));
					att.setColumnName(StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("columnName")));
					att.setViewLabel(StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("viewLabel")));
					att.setAllowNull("true".equalsIgnoreCase(StringUtils.matchNullReplaceWithEmpty(el1.getAttribute("allowNull"))));
					att.setPrimaryKey("true".equalsIgnoreCase(StringUtils.matchNullReplaceWithEmpty(el1.getAttribute("primaryKey"))));
					att.setJavaType(StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("javaType")));
					att.setSqlType(StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("sqlType")));
					String attValue = StringUtils.matchEmptyReplaceWithNull(el1.getAttribute("foreignKey"));
					if(attValue!=null){
						String[] ns = attValue.split("[:]");
						att.setForeignKeyTableName(ns[0]);
						att.setForeignKeyColumnName(ns[1]);
					}					
					NodeList setter = el1.getElementsByTagName("set");
					if(setter!=null && setter.getLength()>0)
					{
						Element m = (Element)setter.item(0);
						att.setSetter(StringUtils.matchEmptyReplaceWithNull(m.getTextContent()));
					}
					NodeList getter = el1.getElementsByTagName("get");
					if(getter!=null && getter.getLength()>0)
					{
						Element m = (Element)getter.item(0);
						att.setGetter(StringUtils.matchEmptyReplaceWithNull(m.getTextContent()));
					}					
					entity.addAttribute(att);					
				}
				
				NodeList rs = el.getElementsByTagName("relationship");
				int rc = rs.getLength();
				for(int r=0; r<rc; r++)
				{
					Relationship rel = new Relationship();
					Element rm = (Element)rs.item(r);
					String type = StringUtils.matchEmptyReplaceWithNull(rm.getAttribute("type"));
					if(type==null) throw new RailsException("Attribute type is require for relationship element. [" + entity.getJavaName() + "]");
					rel.setTypeString(type);
					String prop = StringUtils.matchEmptyReplaceWithNull(rm.getAttribute("properties"));
					if(prop==null) throw new RailsException("Attribute properties are require for relationship element. [" + entity.getJavaName() + "]");
					rel.setProperties(prop.split(","));
					NodeList tgt = rm.getElementsByTagName("target");
					if(tgt!=null && tgt.getLength()==1){
						Element tgtEl = (Element) tgt.item(0);
						Target target = new Target();
						String tname = StringUtils.matchEmptyReplaceWithNull(tgtEl.getAttribute("name"));
						if(tname==null) throw new RailsException("Attribute name is require for target element. [" + entity.getJavaName() + "]");
						target.setName(tname);
						String tprop = StringUtils.matchEmptyReplaceWithNull(tgtEl.getAttribute("properties"));
						if(tprop==null) throw new RailsException("Attribute properties is require for target element. [" + entity.getJavaName() + "]");
						target.setProperties(tprop.split(","));
						rel.setTarget(target);						
					}else{
						throw new RailsException("Only one target allowed for relationship element. [" + entity.getJavaName() + "]");
					}
					NodeList interms = rm.getElementsByTagName("intermediate");
					if(interms!=null && interms.getLength()==1){
						Element intermEl = (Element) interms.item(0);
						Intermediate interm = new Intermediate();
						String iname = StringUtils.matchEmptyReplaceWithNull(intermEl.getAttribute("name"));
						if(iname==null) throw new RailsException("Attribute name is require for intermediate element. [" + entity.getJavaName() + "]");
						interm.setName(iname);
						String lprop = StringUtils.matchEmptyReplaceWithNull(intermEl.getAttribute("leftProperties"));
						if(lprop==null) throw new RailsException("Attribute leftProperties is require for intermediate element. [" + entity.getJavaName() + "]");
						interm.setLeftProperties(lprop.split(","));
						String rprop = StringUtils.matchEmptyReplaceWithNull(intermEl.getAttribute("rightProperties"));
						if(rprop==null) throw new RailsException("Attribute rightProperties is require for intermediate element. [" + entity.getJavaName() + "]");
						interm.setRightProperties(rprop.split(","));						
						rel.setIntermediate(interm);						
					}else if(interms!=null && interms.getLength()>1){
						throw new RailsException("Only one intermediate allowed for relationship element. [" + entity.getJavaName() + "]");
					}					
					entity.addRelationship(rel);
				}
				this.entities.add(entity);
			}			
		}  catch (Exception e) {			
			throw new RailsException(e);
		}finally{
			cleanupRelationship();
		}
		
		
	}
	
	
}
